/**
 * Copyright 2016 Jeffrey Sibbold
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.jsibbold.zoomage;

import com.jsibbold.zoomage.util.AttrValue;
import com.jsibbold.zoomage.util.ResUtil;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Rect;
import ohos.media.image.common.ScaleMode;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.TouchEvent;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * ZoomageView is a pinch-to-zoom extension of, providing a smooth
 * user experience and a very natural feel when zooming and translating. It also supports
 * automatic resetting, and allows for exterior bounds restriction to keep the image within
 * visible window.
 */
public class ZoomageView extends Component implements Component.DrawTask, Component.TouchEventListener, ReboundHelper.ReboundListener {
    private float touchX;
    private float touchY;
    private float touchX2;
    private float touchY2;
    private float ratio = -1;
    private float oMinRatio = 0;
    private float minRatio = 0.6f;
    private float minScale = 0.6f;
    private float maxRatio = 8;
    private float offsetX;
    private float offsetY;

    private float touchPcX;
    private float touchPcY;

    private double speedx;
    private double speedy;
    private float mSpeed = 0.005f;

    private long touchlastTime;

    private PixelMap orgPixelMap;
    private int orgWidth;
    private int orgHeight;
    private float samWidth;
    private float samHeight;
    private PixelMap bgPixelMap;
    private PixelMapHolder bgPixelMapHolder;
    private List<PixelMapDo> hopePixelMaps = new ArrayList<>();
    private Map<Integer, List<PixelMapDo>> samPixelMaps;
    private int currentSam = 1;

    // 背景图缩放比
    private int bgScale = 1;

    // 横向切割比
    private int widthScale = 1;

    // 纵向切割比
    private int heightScale = 1;

    private Paint paint = new Paint();
    private Paint tilePaint = new Paint();
    private AnimatorValue scrollAnimatorValue;
    private AnimatorValue scaleAnimatorValue;

    // 处理双击事件
    private long firstDown;
    private float firstDownTouchX;
    private float firstDownTouchY;
    private long firstUp;
    private float firstUpTouchX;
    private float firstUpTouchY;
    private long secondDown;
    private float secondDownTouchX;
    private float secondDownTouchY;
    private long secondUp;
    private boolean scaleAnimator;

    private boolean isLoad;

    // 是否支持放大缩小
    private boolean zoomEnabled;

    // 双击是否动画
    private boolean animateOnReset;

    // 自动中心
    private boolean autoCenter;

    // 限制边界
    private boolean restrictBounds;

    // 是否支持平移
    private boolean translatable;

    // 是否支持双击放大
    private boolean quickScaleEnabled;

    // 是否释放不在显示区域的切图，内存优化适合高清图
    private boolean memoryOptimization = false;

    private boolean isAnimatorFirst = true;

    @AutoResetMode
    private int autoResetMode;

    // 双击快速放大比例
    private double quickScale = 3;

    private int doubleTapZoomDuration = 200;

    private ReboundHelper mReboundHelper;
    private float oneTouchDownX;
    private float oneTouchDownY;
    private float oneTouchUpX;
    private float oneTouchUpY;

    public ZoomageView(Context context) {
        this(context, null, null);
    }

    public ZoomageView(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    public ZoomageView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(attrSet);
    }

    private PixelMap getPixelMapFromResId(int origin) {
        if (origin == -1) {
            return null;
        }
        Optional<PixelMap> optional = ResUtil.getPixelMap(getContext(), origin);
        PixelMap pixelMap = optional.isPresent() ? optional.get() : null;
        return pixelMap;
    }

    private PixelMap getPixelMapFromPath(String path) {
        if (path == null) {
            return null;
        }
        Optional<PixelMap> optional = ResUtil.getPixelMap(getContext(), path);
        PixelMap pixelMap = optional.isPresent() ? optional.get() : null;
        return pixelMap;
    }

    private PixelMap getPixelMapFromUrl(InputStream origin) {
        Optional<PixelMap> optional = ResUtil.getPixelMap(getContext(), origin);
        PixelMap pixelMap = optional.isPresent() ? optional.get() : null;
        return pixelMap;
    }

    /**
     * set PixelMap with resId
     *
     * @param res the pixelMap resourceId
     */
    public void setResPixelMap(int res) {
        isLoad = true;
        if (orgPixelMap != null) {
            orgPixelMap.release();
            orgPixelMap = null;
        }
        new Thread(
            () -> {
                orgPixelMap = getPixelMapFromResId(res);
                initPixelMap();
            })
            .start();
    }

    /**
     * set PixelMap with url
     *
     * @param url the url
     */
    public void setUrlPixelMap(String url) {
        isLoad = true;
        if (orgPixelMap != null) {
            orgPixelMap.release();
            orgPixelMap = null;
        }
    }

    /**
     * set PixelMap with path
     *
     * @param path the pixelMap path
     */
    public void setFilePath(String path) {
        isLoad = true;
        if (orgPixelMap != null) {
            orgPixelMap.release();
            orgPixelMap = null;
        }
        new Thread(
            () -> {
                orgPixelMap = getPixelMapFromPath(path);
                initPixelMap();
            })
            .start();
    }

    /**
     * set PixelMap
     *
     * @param pixelMap the pixelMap
     */
    public void setPixelMap(PixelMap pixelMap) {
        isLoad = true;
        if (orgPixelMap != null) {
            orgPixelMap.release();
            orgPixelMap = null;
        }
        orgPixelMap = pixelMap;
        initPixelMap();
    }

    private void initPixelMap() {
        new Thread(
            () -> {
                while (getWidth() == 0 || getHeight() == 0) {
                    isLoad = true;
                }
                if (orgPixelMap == null) {
                    return;
                }
                reset();
                orgWidth = orgPixelMap.getImageInfo().size.width;
                orgHeight = orgPixelMap.getImageInfo().size.height;
                minRatio = (float) calculateMinRatio();
                if (oMinRatio > minRatio && oMinRatio < maxRatio) {
                    minRatio = oMinRatio;
                }
                initBgPixelMap();
                initSamPixelMap();
                eventHandler.sendEvent(0);
            })
            .start();
    }

    private void initBgPixelMap() {
        if (minRatio > 1) {
            bgPixelMap = orgPixelMap;
            bgPixelMapHolder = new PixelMapHolder(bgPixelMap);
        } else {
            double sr = minRatio;
            while (sr < 1) {
                sr = sr * 2;
                bgScale = bgScale * 2;
            }
            float wr = getWidth() * 1.0f / orgPixelMap.getImageInfo().size.width;
            while (wr < 1) {
                wr = wr * 2;
                widthScale = widthScale * 2;
            }
            float hr = getHeight() * 1.0f / orgPixelMap.getImageInfo().size.height;
            while (hr < 1) {
                hr = hr * 2;
                heightScale = heightScale * 2;
            }
            samWidth = orgWidth * 1.0f / widthScale;
            samHeight = orgHeight * 1.0f / heightScale;

            PixelMap.InitializationOptions initializationOptions =
                new PixelMap.InitializationOptions();
            initializationOptions.size =
                new Size(
                    orgPixelMap.getImageInfo().size.width / bgScale,
                    orgPixelMap.getImageInfo().size.height / bgScale);
            initializationOptions.pixelFormat = PixelFormat.ARGB_8888;
            bgPixelMap =
                PixelMap.create(
                    orgPixelMap,
                    new Rect(
                        0,
                        0,
                        orgPixelMap.getImageInfo().size.width,
                        orgPixelMap.getImageInfo().size.height),
                    initializationOptions);
            bgPixelMapHolder = new PixelMapHolder(bgPixelMap);
        }
    }

    private void reset() {
        touchX = 0;
        touchY = 0;
        touchX2 = 0;
        touchY2 = 0;
        ratio = -1;
        minRatio = 0;
        offsetX = 0;
        offsetY = 0;

        touchPcX = 0;
        touchPcY = 0;

        speedx = 0;
        speedy = 0;
        touchlastTime = 0;
        currentSam = 1;
        bgScale = 1;
        widthScale = 1;
        heightScale = 1;
    }

    /**
     * Reset image back to its original size. Will snap back to original size
     * if animation on reset is disabled via {@link #setAnimateOnReset(boolean)}.
     */
    public void setReset() {
        reset(animateOnReset);
    }

    /**
     * Reset image back to its starting size. If {@code animate} is false, image
     * will snap back to its original size.
     *
     * @param animate animate the image back to its starting size
     */
    public void reset(final boolean animate) {
        float toX = (float) ((getWidth() - orgPixelMap.getImageInfo().size.width * minRatio) / 2);
        float toY = (float) ((getHeight() - orgPixelMap.getImageInfo().size.height * minRatio) / 2);

        if (animate) {
            startScaleAnimator(offsetX, toX, offsetY, toY, ratio, minRatio, touchX, touchY, doubleTapZoomDuration);
        } else {
            startScaleAnimator(offsetX, toX, offsetY, toY, ratio, minRatio, touchX, touchY, 0);
        }
    }

    private double calculateMinRatio() {
        if (orgPixelMap != null) {
            return Math.min(getWidth() * 1.0f / orgPixelMap.getImageInfo().size.width, getHeight() * 1.0f / orgPixelMap.getImageInfo().size.height);
        } else {
            return minRatio;
        }
    }

    private EventHandler eventHandler =
        new EventHandler(EventRunner.getMainEventRunner()) {
            @Override
            protected void processEvent(InnerEvent event) {
                isLoad = false;
                invalidate();
            }
        };

    public void setZoomEnabled(boolean zoomEnabled) {
        this.zoomEnabled = zoomEnabled;
        if (orgPixelMap != null) {
            invalidate();
        }
    }

    public void setAnimateOnReset(boolean animateOnReset) {
        this.animateOnReset = animateOnReset;
        if (orgPixelMap != null) {
            invalidate();
        }
    }

    public void setAutoCenter(final boolean autoCenter) {
        this.autoCenter = autoCenter;
    }

    public boolean getAutoCenter() {
        return autoCenter;
    }

    public void setRestrictBounds(boolean restrictBounds) {
        this.restrictBounds = restrictBounds;
        if (orgPixelMap != null) {
            invalidate();
        }
    }

    public void setPanEnabled(boolean translatable) {
        this.translatable = translatable;
        if (orgPixelMap != null && !translatable) {
            offsetX = (float) (getWidth() - orgPixelMap.getImageInfo().size.width * ratio) / 2;
            offsetY = (float) (getHeight() - orgPixelMap.getImageInfo().size.height * ratio) / 2;
            invalidate();
        }
    }

    public void setDoubleTapZoomScale(float quickScale) {
        if (quickScale > maxRatio && orgPixelMap != null) {
            this.quickScale = maxRatio;
        } else if (quickScale < minScale && orgPixelMap != null) {
            this.quickScale = minScale;
        } else {
            this.quickScale = quickScale;
        }
    }

    public void setMemoryOptimization(boolean memoryOptimization) {
        if (this.memoryOptimization != memoryOptimization) {
            this.memoryOptimization = memoryOptimization;
            invalidate();
        }
    }

    public boolean isQuickScaleEnabled() {
        return quickScaleEnabled;
    }

    public boolean isZoomEnabled() {
        return zoomEnabled;
    }

    public boolean isPanEnabled() {
        return translatable;
    }

    public boolean isRestrictBounds() {
        return restrictBounds;
    }

    public boolean isAnimateOnReset() {
        return animateOnReset;
    }

    public void setQuickScaleEnabled(boolean quickScaleEnabled) {
        this.quickScaleEnabled = quickScaleEnabled;
    }

    public boolean isMemoryOptimization() {
        return memoryOptimization;
    }

    public void setMaxRatio(float scale) {
        this.maxRatio = scale;
        if (orgPixelMap != null && ratio > maxRatio) {
            ratio = maxRatio;
            invalidate();
        }
    }

    public void setRatio(float scale) {
        this.ratio = scale;
        if (orgPixelMap != null) {
            invalidate();
        }
    }

    public void setMinScale(float scale) {
        this.minScale = scale;
        if (orgPixelMap != null && ratio < minScale) {
            ratio = minScale;
            invalidate();
        }
    }

    public float getMinScale() {
        return oMinRatio;
    }

    public double getMaxScale() {
        return maxRatio;
    }

    public final void resetScaleAndCenter() {
        ratio = minRatio;
        invalidate();
    }

    public final boolean isReady() {
        return !isLoad && orgPixelMap != null && !orgPixelMap.isReleased();
    }

    public final boolean isImageLoaded() {
        return isLoad;
    }

    public final void setDoubleTapZoomDuration(int durationMs) {
        this.doubleTapZoomDuration = Math.max(0, durationMs);
    }

    private float getTouchX(TouchEvent touchEvent, int index) {
        float getX = 0;
        if (touchEvent.getPointerCount() > index) {
            int[] xy = getLocationOnScreen();
            if (xy != null && xy.length == 2) {
                getX = touchEvent.getPointerScreenPosition(index).getX() - xy[0];
            } else {
                getX = touchEvent.getPointerPosition(index).getX();
            }
        }
        return getX;
    }

    private float getTouchY(TouchEvent touchEvent, int index) {
        float getY = 0;
        if (touchEvent.getPointerCount() > index) {
            int[] xy = getLocationOnScreen();
            if (xy != null && xy.length == 2) {
                getY = touchEvent.getPointerScreenPosition(index).getY() - xy[1];
            } else {
                getY = touchEvent.getPointerPosition(index).getY();
            }
        }
        return getY;
    }

    private void init(AttrSet attrSet) {
        translatable = AttrValue.get(attrSet, "zoomage_translatable", true);
        zoomEnabled = AttrValue.get(attrSet, "zoomage_zoomable", true);
        animateOnReset = AttrValue.get(attrSet, "zoomage_animateOnReset", true);
        autoCenter = AttrValue.get(attrSet, "zoomage_autoCenter", true);
        restrictBounds = AttrValue.get(attrSet, "zoomage_restrictBounds", false);
        quickScaleEnabled = AttrValue.get(attrSet, "zoomage_doubleTapToZoom", true);
        minScale = AttrValue.get(attrSet, "zoomage_minScale", 0.6f);
        maxRatio = AttrValue.get(attrSet, "zoomage_maxScale", 8f);
        quickScale = AttrValue.get(attrSet, "zoomage_doubleTapToZoomScaleFactor", 3f);
        autoResetMode = AttrValue.get(attrSet, "zoomage_autoResetMode", AutoResetMode.UNDER);

        scrollAnimatorValue = new AnimatorValue();
        scrollAnimatorValue.setDuration(20000);
        scrollAnimatorValue.setCurveType(Animator.CurveType.LINEAR);
        paint.setAntiAlias(true);
        paint.setFilterBitmap(true);
        paint.setDither(true);
        setTouchEventListener(this);
        addDrawTask(this);

        mReboundHelper = new ReboundHelper(this::update);
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        if (scrollAnimatorValue != null && scrollAnimatorValue.isRunning()) {
            scrollAnimatorValue.stop();
        }
        if (scaleAnimator) {
            return true;
        }

        if (touchEvent.getPointerCount() >= 3) {
            if (touchEvent.getAction() == TouchEvent.OTHER_POINT_UP
                || touchEvent.getAction() == TouchEvent.OTHER_POINT_UP) {
                doThreeTouchUp(touchEvent);
            }
            return true;
        }
        if (touchEvent.getPointerCount() >= 2) {
            if (touchEvent.getAction() == TouchEvent.OTHER_POINT_DOWN
                || touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
                doTwoTouchDown(touchEvent);
            } else if (touchEvent.getAction() == TouchEvent.POINT_MOVE) {
                doTwoTouchMove(touchEvent);
            } else if (touchEvent.getAction() == TouchEvent.OTHER_POINT_UP
                || touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP) {
                doTwoTouchUp(touchEvent);
            }
            return true;
        } else if (touchEvent.getPointerCount() == 1) {
            if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
                doOneTouchDown(touchEvent);
            } else if (touchEvent.getAction() == TouchEvent.POINT_MOVE) {
                if (ratio > minRatio && translatable) {
                    doOneTouchMove(touchEvent);
                }
                invalidate();
                return true;
            } else if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP) {
                doOneTouchUp(touchEvent);
            }
        }

        if (scaleAnimator) return true;

        if (touchEvent.getAction() == TouchEvent.CANCEL || touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP) {
            resetImage();
        }
        return true;
    }

    @AutoResetMode
    public int getAutoResetMode() {
        return autoResetMode;
    }

    /**
     * Set the {@link AutoResetMode} mode for the image.
     *
     * @param autoReset the desired mode, one of {@link AutoResetMode#OVER OVER}, {@link AutoResetMode#UNDER UNDER},
     *                  {@link AutoResetMode#ALWAYS ALWAYS}, or {@link AutoResetMode#NEVER NEVER}
     */
    public void setAutoResetMode(@AutoResetMode final int autoReset) {
        this.autoResetMode = autoReset;
    }

    private void resetImage() {
        switch (autoResetMode) {
            case AutoResetMode.UNDER:
                if (minRatio > ratio && (oneTouchDownX != oneTouchUpX || oneTouchDownY != oneTouchUpY)) {
                    setReset();
                } else {
                    center();
                }
                break;
            case AutoResetMode.OVER:
                if (minRatio < ratio && (oneTouchDownX != oneTouchUpX || oneTouchDownY != oneTouchUpY)) {
                    setReset();
                } else {
                    center();
                }
                break;
            case AutoResetMode.ALWAYS:
                if (oneTouchDownX != oneTouchUpX || oneTouchDownY != oneTouchUpY) {
                    setReset();
                } else {
                    center();
                }
                break;
            case AutoResetMode.NEVER:
                center();
        }
    }

    private void center() {
        if (autoCenter) {
            float toX;
            if (orgPixelMap.getImageInfo().size.width * ratio < getWidth()) {
                toX = offsetX < 0 ? 0 : offsetX;
                if (offsetX + orgPixelMap.getImageInfo().size.width * ratio > getWidth()) {
                    toX = (float) (getWidth() - orgPixelMap.getImageInfo().size.width * ratio);
                }
            } else {
                toX = offsetX > 0 ? 0 : offsetX;
                if (offsetX + orgPixelMap.getImageInfo().size.width * ratio < getWidth()) {
                    toX = (float) (getWidth() - orgPixelMap.getImageInfo().size.width * ratio);
                }
            }


            float toY;
            if (orgPixelMap.getImageInfo().size.height * ratio < getHeight()) {
                toY = offsetY < 0 ? 0 : offsetY;
                if (offsetY + orgPixelMap.getImageInfo().size.height * ratio > getHeight()) {
                    toY = (float) (getHeight() - orgPixelMap.getImageInfo().size.height * ratio);
                }
            } else {
                toY = offsetY > 0 ? 0 : offsetY;
                if (offsetY + orgPixelMap.getImageInfo().size.height * ratio < getHeight()) {
                    toY = (float) (getHeight() - orgPixelMap.getImageInfo().size.height * ratio);
                }
            }
            mReboundHelper.rebound(offsetX, offsetY, toX, toY);
        }
    }


    private void doThreeTouchUp(TouchEvent touchEvent) {
        if (touchEvent.getIndex() == 0) {
            touchX2 = getTouchX(touchEvent, 2);
            touchY2 = getTouchY(touchEvent, 2);
            touchX = getTouchX(touchEvent, 1);
            touchY = getTouchY(touchEvent, 1);
            touchPcX =
                (float)
                    ((((getTouchX(touchEvent, 0) + getTouchX(touchEvent, 1)) / 2) - offsetX)
                        / ratio);
            touchPcY =
                (float)
                    ((((getTouchY(touchEvent, 0) + getTouchY(touchEvent, 1)) / 2) - offsetY)
                        / ratio);
        } else if (touchEvent.getIndex() == 1) {
            touchX2 = getTouchX(touchEvent, 2);
            touchY2 = getTouchY(touchEvent, 2);
            touchX = getTouchX(touchEvent, 0);
            touchY = getTouchY(touchEvent, 0);
            touchPcX =
                (float)
                    ((((getTouchX(touchEvent, 0) + getTouchX(touchEvent, 1)) / 2) - offsetX)
                        / ratio);
            touchPcY =
                (float)
                    ((((getTouchY(touchEvent, 0) + getTouchY(touchEvent, 1)) / 2) - offsetY)
                        / ratio);
        }
    }

    private void doTwoTouchDown(TouchEvent touchEvent) {
        touchX2 = getTouchX(touchEvent, 1);
        touchY2 = getTouchY(touchEvent, 1);
        touchX = getTouchX(touchEvent, 0);
        touchY = getTouchY(touchEvent, 0);

        touchPcX =
            (float)
                ((((getTouchX(touchEvent, 0) + getTouchX(touchEvent, 1)) / 2) - offsetX)
                    / ratio);
        touchPcY =
            (float)
                ((((getTouchY(touchEvent, 0) + getTouchY(touchEvent, 1)) / 2) - offsetY)
                    / ratio);
    }

    private void doTwoTouchMove(TouchEvent touchEvent) {
        double yuanRatio = ratio;
        if (zoomEnabled) {
            ratio =
                (float) (ratio
                    * Math.sqrt(
                    (getTouchX(touchEvent, 0) - getTouchX(touchEvent, 1))
                        * (getTouchX(touchEvent, 0)
                        - getTouchX(touchEvent, 1))
                        + (getTouchY(touchEvent, 0) - getTouchY(touchEvent, 1))
                        * (getTouchY(touchEvent, 0)
                        - getTouchY(touchEvent, 1)))
                    / Math.sqrt(
                    (touchX - touchX2) * (touchX - touchX2)
                        + (touchY - touchY2) * (touchY - touchY2)));
            if (ratio > maxRatio) {
                ratio = maxRatio;
            }
            if (ratio < minScale) {
                ratio = minScale;
            }
        }


        if (orgPixelMap.getImageInfo().size.width * ratio > getWidth() || orgPixelMap.getImageInfo().size.height * ratio > getHeight()) {
            offsetX +=
                (getTouchX(touchEvent, 0) + getTouchX(touchEvent, 1)) / 2 - (touchX + touchX2) / 2;
            offsetX -= touchPcX * ratio - touchPcX * yuanRatio;

            offsetY +=
                (getTouchY(touchEvent, 0) + getTouchY(touchEvent, 1)) / 2 - (touchY + touchY2) / 2;
            offsetY -= touchPcY * ratio - touchPcY * yuanRatio;
        } else {
            offsetX -= touchPcX * ratio - touchPcX * yuanRatio;
            offsetY -= touchPcY * ratio - touchPcY * yuanRatio;
        }

        touchX2 = getTouchX(touchEvent, 1);
        touchY2 = getTouchY(touchEvent, 1);
        touchX = getTouchX(touchEvent, 0);
        touchY = getTouchY(touchEvent, 0);
        invalidate();
    }

    private void doTwoTouchUp(TouchEvent touchEvent) {
        if (touchEvent.getIndex() == 0 && touchEvent.getPointerCount() == 2) {
            touchX = getTouchX(touchEvent, 1);
            touchY = getTouchY(touchEvent, 1);
        }
    }

    private void doOneTouchDown(TouchEvent touchEvent) {
        touchX = getTouchX(touchEvent, 0);
        touchY = getTouchY(touchEvent, 0);
        oneTouchDownX = touchX;
        oneTouchDownY = touchY;
        speedx = 0;
        speedy = 0;
        System.out.println("doOneTouchDown====================" + touchX);
        long thisTime = System.currentTimeMillis();
        if (thisTime - firstDown > 600) {
            resetDoubleTouch();
            firstDown = thisTime;
            firstDownTouchX = touchX;
            firstDownTouchY = touchY;
        } else if (secondDown == 0
            && thisTime - firstUp < 300
            && Math.abs(touchX - firstUpTouchX) < vp2px(30)
            && Math.abs(touchY - firstUpTouchY) < vp2px(30)) {
            secondDown = thisTime;
            secondDownTouchX = touchX;
            secondDownTouchY = touchY;
        } else {
            resetDoubleTouch();
            firstDown = thisTime;
            firstDownTouchX = touchX;
            firstDownTouchY = touchY;
        }
    }

    private void doOneTouchMove(TouchEvent touchEvent) {
        long thisTime = System.currentTimeMillis();
        speedx = (getTouchX(touchEvent, 0) - touchX) / (thisTime - touchlastTime);
        speedy = (getTouchY(touchEvent, 0) - touchY) / (thisTime - touchlastTime);
        offsetX += getTouchX(touchEvent, 0) - touchX;
        offsetY += getTouchY(touchEvent, 0) - touchY;
        touchY = getTouchY(touchEvent, 0);
        touchX = getTouchX(touchEvent, 0);
        touchlastTime = System.currentTimeMillis();
    }

    private void doOneTouchUp(TouchEvent touchEvent) {
        touchX = getTouchX(touchEvent, 0);
        System.out.println("doOneTouchUp====================" + touchX);
        touchY = getTouchY(touchEvent, 0);
        oneTouchUpX = touchX;
        oneTouchUpY = touchY;
        long thisTime = System.currentTimeMillis();
        if (firstUp == 0
            && thisTime - firstDown < 300
            && Math.abs(touchX - firstDownTouchX) < vp2px(30)
            && Math.abs(touchY - firstDownTouchY) < vp2px(30)) {
            firstUp = thisTime;
            firstUpTouchX = touchX;
            firstUpTouchY = touchY;
        } else if (secondUp == 0
            && thisTime - secondDown < 300
            && Math.abs(touchX - secondDownTouchX) < vp2px(30)
            && Math.abs(touchY - secondDownTouchY) < vp2px(30)) {
            secondUp = thisTime;
            if (orgPixelMap != null && translatable || zoomEnabled) {
                calScaleAnimator(touchX, touchY, ratio);
            }
            resetDoubleTouch();
        } else {
            resetDoubleTouch();
        }
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        tilePaint.setColor(Color.WHITE);
        canvas.drawRect(new RectFloat(0, 0, getWidth(), getHeight()), tilePaint);
        if (orgPixelMap != null && bgPixelMap != null && !isLoad) {
            if (ratio == -1) {
                ratio = minRatio;
            }
            if (restrictBounds) {
                checkZoom();
            }
            if (!scaleAnimator && isAnimatorFirst) {
                isAnimatorFirst = false;
                offsetY = (float) (getHeight() - orgPixelMap.getImageInfo().size.height * ratio) / 2;
            }

            RectFloat rect =
                new RectFloat(
                    (float) (-offsetX / ratio),
                    (float) (-offsetY / ratio),
                    (float)
                        (orgPixelMap.getImageInfo().size.width
                            - ((orgPixelMap.getImageInfo().size.width) * ratio
                            + offsetX
                            - getWidth())
                            / ratio),
                    (float)
                        (orgPixelMap.getImageInfo().size.height
                            - ((orgPixelMap.getImageInfo().size.height) * ratio
                            + offsetY
                            - getHeight())
                            / ratio));

            calCurrentSam();
            if (bgPixelMap != null) {
                rect =
                    new RectFloat(
                        rect.left / bgScale,
                        rect.top / bgScale,
                        rect.right / bgScale,
                        rect.bottom / bgScale);
                RectFloat rectf = new RectFloat(8, 8, getWidth() - 8, getHeight() - 8);
                if (bgPixelMapHolder == null) {
                    bgPixelMapHolder = new PixelMapHolder(bgPixelMap);
                }
                canvas.drawPixelMapHolderRect(bgPixelMapHolder, rect, rectf, paint);
            }
        }
    }

    private void resetDoubleTouch() {
        firstDown = 0;
        firstUp = 0;
        secondUp = 0;
        secondDown = 0;
    }

    private void calCurrentSam() {
        int ratioOrg = 1;
        while (ratioOrg * ratio < 1) {
            ratioOrg = ratioOrg * 2;
        }
        currentSam = ratioOrg;
    }

    private void initSamPixelMap() {
        samPixelMaps = new HashMap<>();
        for (int i = 1; i <= bgScale; i = i * 2) {
            List<PixelMapDo> pixelMapDos = new ArrayList<>();
            for (int w = 0; w < widthScale; w++) {
                for (int h = 0; h < heightScale; h++) {
                    PixelMapDo pixelMapDo = new PixelMapDo();
                    pixelMapDo.rect =
                        new RectFloat(samWidth * w, samHeight * h, samWidth * (w + 1), samHeight * (h + 1));
                    pixelMapDos.add(pixelMapDo);
                }
            }
            samPixelMaps.put(i, pixelMapDos);
        }
    }

    private void drawSamPixelMap(RectFloat rect, int sam, Canvas canvas, double ratio) {
        if (samPixelMaps != null) {
            List<PixelMapDo> pixelMapDos = samPixelMaps.get(sam);
            for (int i = 0; i < pixelMapDos.size(); i++) {
                PixelMapDo pixelMapDo = pixelMapDos.get(i);
                if (pixelMapDo != null && pixelMapDo.inVisible) {
                    if (pixelMapDo.isFinish && pixelMapDo.pixelMap != null && !pixelMapDo.pixelMap.isReleased()) {
                        RectFloat samSrc =
                            new RectFloat(
                                0,
                                0,
                                pixelMapDo.pixelMap.getImageInfo().size.width,
                                pixelMapDo.pixelMap.getImageInfo().size.height);
                        RectFloat screen =
                            new RectFloat(
                                (int) ((pixelMapDo.rect.left - rect.left) * ratio),
                                (int) ((pixelMapDo.rect.top - rect.top) * ratio),
                                (int) ((pixelMapDo.rect.right - rect.left) * ratio),
                                (int) ((pixelMapDo.rect.bottom - rect.top) * ratio));
                        if (pixelMapDo.pixelMapHolder == null) {
                            pixelMapDo.pixelMapHolder = new PixelMapHolder(pixelMapDo.pixelMap);
                        }
                        canvas.drawPixelMapHolderRect(pixelMapDo.pixelMapHolder, samSrc, screen, paint);

                    } else {
                        PixelMapDo samBigpixelMapDo = hopeSamBigPixmap(i, sam);
                        if (samBigpixelMapDo != null) {
                            RectFloat samSrc =
                                new RectFloat(
                                    0,
                                    0,
                                    samBigpixelMapDo.pixelMap.getImageInfo().size.width,
                                    samBigpixelMapDo.pixelMap.getImageInfo().size.height);
                            RectFloat screen =
                                new RectFloat(
                                    (int) ((pixelMapDo.rect.left - rect.left) * ratio),
                                    (int) ((pixelMapDo.rect.top - rect.top) * ratio),
                                    (int) ((pixelMapDo.rect.right - rect.left) * ratio),
                                    (int) ((pixelMapDo.rect.bottom - rect.top) * ratio));
                            if (samBigpixelMapDo.pixelMapHolder == null) {
                                samBigpixelMapDo.pixelMapHolder = new PixelMapHolder(samBigpixelMapDo.pixelMap);
                            }
                            canvas.drawPixelMapHolderRect(
                                samBigpixelMapDo.pixelMapHolder, samSrc, screen, paint);
                        }
                    }
                }
            }
        }
    }

    private boolean checkZoom() {
        boolean isMoveToStrock = false;
        int width = getWidth();
        int height = getHeight();
        if (width - orgPixelMap.getImageInfo().size.width * ratio > offsetX) {
            offsetX = width - orgPixelMap.getImageInfo().size.width * ratio;
            isMoveToStrock = true;
        }
        if (0 < offsetX) {
            offsetX = 0;
            isMoveToStrock = true;
        }
        if (height - orgPixelMap.getImageInfo().size.height * ratio > offsetY) {
            offsetY = height - orgPixelMap.getImageInfo().size.height * ratio;
            isMoveToStrock = true;
        }
        if (0 < offsetY) {
            offsetY = 0;
            isMoveToStrock = true;
        }
        if (width > orgPixelMap.getImageInfo().size.width * ratio) {
            offsetX = (width - orgPixelMap.getImageInfo().size.width * ratio) / 2;
            isMoveToStrock = true;
        }
        if (height > orgPixelMap.getImageInfo().size.height * ratio) {
            offsetY = (height - orgPixelMap.getImageInfo().size.height * ratio) / 2;
            isMoveToStrock = true;
        }
        return isMoveToStrock;
    }

    private boolean isXRect(RectFloat r1, RectFloat r2) {
        if (Math.max(r1.left, r2.left) >= Math.min(r1.right, r2.right)
            || Math.max(r1.top, r2.top) >= Math.min(r1.bottom, r2.bottom)) {
            return false;
        }
        return true;
    }

    private RectFloat getXRect(RectFloat r1, RectFloat r2) {
        if (isXRect(r1, r2)) {
            return new RectFloat(
                Math.max(r1.left, r2.left),
                Math.max(r1.top, r2.top),
                Math.min(r1.right, r2.right),
                Math.min(r1.bottom, r2.bottom));
        }
        return new RectFloat();
    }

    private boolean checkHashMap(RectFloat rect) {
        boolean result = true;
        if (samPixelMaps == null) {
            return false;
        }
        List<PixelMapDo> pixelMapDos = samPixelMaps.get(currentSam);
        if (pixelMapDos == null) {
            return false;
        }
        for (int sam = 1; sam < bgScale; sam = sam * 2) {
            for (int i = 0; i < pixelMapDos.size(); i++) {
                PixelMapDo pixelMapDo = pixelMapDos.get(i);
                if (pixelMapDo != null && isXRect(rect, pixelMapDo.rect)) {
                    pixelMapDo.inVisible = true;
                    if (sam == currentSam) {
                        if (pixelMapDo.pixelMap == null || pixelMapDo.pixelMap.isReleased()) {
                            pixelMapDo.needHope = true;
                            doHopePixelMaps(pixelMapDo);
                            if (hopeSamBigPixmap(i, currentSam) == null) {
                                result = false;
                            }
                        }
                    }
                } else {
                    pixelMapDo.inVisible = false;
                }
            }
        }
        return result;
    }

    private PixelMapDo hopeSamBigPixmap(int index, int sam) {
        while (sam < bgScale) {
            sam = sam * 2;
            if (samPixelMaps != null) {
                List<PixelMapDo> pixelMapDos = samPixelMaps.get(sam);
                if (pixelMapDos != null && pixelMapDos.size() > index) {
                    PixelMapDo pixelMapDo = pixelMapDos.get(index);
                    if (pixelMapDo != null
                        && pixelMapDo.isFinish
                        && pixelMapDo.pixelMap != null
                        && !pixelMapDo.pixelMap.isReleased()) {
                        return pixelMapDo;
                    }
                }
            }
        }
        return null;
    }

    private Thread hopeThread;

    private void doHopePixelMaps(PixelMapDo pixelMapDo) {
        if (hopePixelMaps == null) {
            hopePixelMaps = new ArrayList<>();
        }
        if (!hopePixelMaps.contains(pixelMapDo)) {
            hopePixelMaps.add(pixelMapDo);
        }

        if (hopeThread == null || !hopeThread.isAlive()) {
            hopeThread =
                new Thread(
                    () -> {
                        while (true) {
                            boolean needStop = true;
                            if (hopePixelMaps != null) {
                                for (int i = 0; i < hopePixelMaps.size(); i++) {
                                    if (pixelMapDo != null
                                        && pixelMapDo.pixelMap == null
                                        && !pixelMapDo.isFinish
                                        && pixelMapDo.needHope) {
                                        needStop = false;
                                    }
                                }
                            }
                            if (hopePixelMaps == null || needStop) {
                                break;
                            }
                            createHopePixelMaps();
                        }
                    });
            hopeThread.start();
        }
    }

    private void createHopePixelMaps() {
        for (int i = 0; i < hopePixelMaps.size(); i++) {
            PixelMapDo pixelMapDo1 = hopePixelMaps.get(i);
            if (pixelMapDo1 != null
                && pixelMapDo1.pixelMap == null
                && !pixelMapDo1.isFinish
                && pixelMapDo1.needHope) {
                PixelMap.InitializationOptions initializationOptions =
                    new PixelMap.InitializationOptions();
                initializationOptions.size = new Size((int) samWidth, (int) samHeight);
                initializationOptions.scaleMode = ScaleMode.CENTER_CROP;
                initializationOptions.pixelFormat = PixelFormat.RGB_565;
                Rect rect =
                    new Rect(
                        (int) pixelMapDo1.rect.left,
                        (int) pixelMapDo1.rect.top,
                        (int) samWidth,
                        (int) samHeight);
                pixelMapDo1.pixelMap =
                    PixelMap.create(orgPixelMap, rect, initializationOptions);
                pixelMapDo1.pixelMapHolder = new PixelMapHolder(pixelMapDo1.pixelMap);
                if (pixelMapDo1.pixelMap != null
                    && pixelMapDo1.pixelMap.getImageInfo().size.width > 0
                    && pixelMapDo1.pixelMap.getImageInfo().size.height > 0) {
                    pixelMapDo1.isFinish = true;
                    pixelMapDo1.needHope = false;
                }
            } else if (memoryOptimization && pixelMapDo1.pixelMap != null
                && !pixelMapDo1.inVisible
                && pixelMapDo1.isFinish
                && !pixelMapDo1.pixelMap.isReleased()) {
                pixelMapDo1.pixelMapHolder = null;
                pixelMapDo1.pixelMap.release();
                pixelMapDo1.pixelMap = null;
                pixelMapDo1.isFinish = false;
            }
        }
    }

    @Override
    public void update(float offsetX, float offsetY) {

        this.offsetX = offsetX;
        this.offsetY = offsetY;
        invalidate();
    }

    private static class PixelMapDo {
        boolean inVisible;
        RectFloat rect;
        PixelMap pixelMap;
        PixelMapHolder pixelMapHolder;
        boolean isFinish;
        boolean needHope;
    }

    private long lastAnimatorTime;
    private double lastAnimatorSpx;
    private double lastAnimatorSpy;

    private int vp2px(float vp) {
        return (int) (getResourceManager().getDeviceCapability().screenDensity / 160f * vp);
    }

    private void calScaleAnimator(float touchX, float touchY, double ratio) {
        if (minRatio < ratio || minRatio > ratio) {
            // 复位
            float toX = 0;
            float toY = 0;
            if (getWidth() > orgPixelMap.getImageInfo().size.width * minRatio) {
                toX = (float) (getWidth() - orgPixelMap.getImageInfo().size.width * minRatio) / 2;
            }
            if (getHeight() > orgPixelMap.getImageInfo().size.height * minRatio) {
                toY = (float) (getHeight() - orgPixelMap.getImageInfo().size.height * minRatio) / 2;
            }
            if (animateOnReset) {
                startScaleAnimator(offsetX, toX, offsetY, toY, ratio, minRatio, touchX, touchY, doubleTapZoomDuration);
            } else {
                startScaleAnimator(offsetX, toX, offsetY, toY, ratio, minRatio, touchX, touchY, 0);
            }
        } else {
            // 放大
            float toX = (float) (touchX - (touchX - offsetX) / ratio * quickScale);
            float toY = (float) (touchY - (touchY - offsetY) / ratio * quickScale);

            if (toX < getWidth() - orgPixelMap.getImageInfo().size.width * quickScale) {
                toX = (float) (getWidth() - orgPixelMap.getImageInfo().size.width * quickScale);
            }
            if (toY < getHeight() - orgPixelMap.getImageInfo().size.height * quickScale) {
                toY = (float) (getHeight() - orgPixelMap.getImageInfo().size.height * quickScale);
            }
            if (toX > 0) {
                toX = 0;
            }
            if (toY > 0) {
                toY = 0;
            }
            if (getWidth() > orgPixelMap.getImageInfo().size.width * quickScale) {
                toX = (float) (getWidth() - orgPixelMap.getImageInfo().size.width * quickScale) / 2;
            }
            if (getHeight() > orgPixelMap.getImageInfo().size.height * quickScale) {
                toY = (float) (getHeight() - orgPixelMap.getImageInfo().size.height * quickScale) / 2;
            }

            startScaleAnimator(offsetX, toX, offsetY, toY, ratio, quickScale, touchX, touchY, doubleTapZoomDuration);
        }
    }

    private void startScaleAnimator(
        float fromX,
        float toX,
        float fromY,
        float toY,
        double fromRatio,
        double toRatio,
        float touchX,
        float touchY,
        int duration) {
        if (scaleAnimatorValue == null) {
            scaleAnimatorValue = new AnimatorValue();
            scaleAnimatorValue.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);
        }
        scaleAnimatorValue.setDuration(duration);
        scaleAnimatorValue.setValueUpdateListener(
            (animatorValue, v) -> {
                offsetX = fromX + (toX - fromX) * v;
                offsetY = fromY + (toY - fromY) * v;
                System.out.println("--------------=" + ratio);
                ratio = (float) (fromRatio + (toRatio - fromRatio) * v);
                System.out.println("--------------=" + ratio);
                getContext()
                    .getUITaskDispatcher()
                    .asyncDispatch(
                        () -> invalidate());
            });
        scaleAnimatorValue.setStateChangedListener(
            new Animator.StateChangedListener() {
                @Override
                public void onStart(Animator animator) {
                }

                @Override
                public void onStop(Animator animator) {
                }

                @Override
                public void onCancel(Animator animator) {
                }

                @Override
                public void onEnd(Animator animator) {
                    scaleAnimator = false;
                    getContext()
                        .getUITaskDispatcher()
                        .asyncDispatch(
                            () -> invalidate());
                }

                @Override
                public void onPause(Animator animator) {
                }

                @Override
                public void onResume(Animator animator) {
                }
            });
        scaleAnimator = true;
        scaleAnimatorValue.start();
    }

    private void startScrollAnimator() {
        if (scrollAnimatorValue == null) {
            scrollAnimatorValue = new AnimatorValue();
            scrollAnimatorValue.setDuration(20000);
            scrollAnimatorValue.setCurveType(Animator.CurveType.LINEAR);
        }
        lastAnimatorTime = touchlastTime;
        lastAnimatorSpx = speedx;
        lastAnimatorSpy = speedy;
        if (lastAnimatorSpx == 0 && lastAnimatorSpy == 0) {
            return;
        }

        scrollAnimatorValue.setValueUpdateListener(
            (animatorValue, v) -> {
                long time = System.currentTimeMillis();
                double speed = Math.sqrt(speedx * speedx + speedy * speedy);
                double spa = speed - (time - touchlastTime) * mSpeed;
                double spx = 0;
                double spy = 0;
                if (speedx == 0) {
                    spy = spa;
                } else if (speedy == 0) {
                    spx = spa;
                } else {
                    spx = Math.sqrt(spa * spa / (1 + (speedy * speedy) / (speedx * speedx)));
                    if (speedx < 0) {
                        spx = -spx;
                    }
                    spy = Math.sqrt(spa * spa / (1 + (speedx * speedx) / (speedy * speedy)));
                    if (speedy < 0) {
                        spy = -spy;
                    }
                }
                if (spa <= 0) {
                    spa = 0;
                    spx = 0;
                    spy = 0;
                    scrollAnimatorValue.stop();
                }
                offsetX += (time - lastAnimatorTime) * (lastAnimatorSpx + spx) / 2;
                offsetY += (time - lastAnimatorTime) * (lastAnimatorSpy + spy) / 2;
                lastAnimatorTime = time;
                lastAnimatorSpx = spx;
                lastAnimatorSpy = spy;
                getContext()
                    .getUITaskDispatcher()
                    .asyncDispatch(
                        () -> invalidate());
            });
        scrollAnimatorValue.start();
    }
}
